// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/audio_renderer_mixer_input.h"

#include <cmath>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/trace_event/trace_event.h"
#include "media/base/audio_renderer_mixer.h"
#include "media/base/audio_renderer_mixer_pool.h"
#include "media/base/audio_timestamp_helper.h"

namespace media {

AudioRendererMixerInput::AudioRendererMixerInput(
    AudioRendererMixerPool* mixer_pool,
    int owner_id,
    const std::string& device_id,
    const url::Origin& security_origin,
    AudioLatency::LatencyType latency)
    : mixer_pool_(mixer_pool)
    , started_(false)
    , playing_(false)
    , volume_(1.0f)
    , owner_id_(owner_id)
    , device_id_(device_id)
    , security_origin_(security_origin)
    , latency_(latency)
    , mixer_(nullptr)
    , callback_(nullptr)
    , error_cb_(base::Bind(&AudioRendererMixerInput::OnRenderError,
          base::Unretained(this)))
{
    DCHECK(mixer_pool_);
}

AudioRendererMixerInput::~AudioRendererMixerInput()
{
    DCHECK(!started_);
    DCHECK(!mixer_);
}

void AudioRendererMixerInput::Initialize(
    const AudioParameters& params,
    AudioRendererSink::RenderCallback* callback)
{
    DCHECK(!started_);
    DCHECK(!mixer_);
    DCHECK(callback);

    params_ = params;
    callback_ = callback;
}

void AudioRendererMixerInput::Start()
{
    DCHECK(!started_);
    DCHECK(!mixer_);
    DCHECK(callback_); // Initialized.

    started_ = true;
    mixer_ = mixer_pool_->GetMixer(owner_id_, params_, latency_, device_id_,
        security_origin_, nullptr);
    if (!mixer_) {
        callback_->OnRenderError();
        return;
    }

    // Note: OnRenderError() may be called immediately after this call returns.
    mixer_->AddErrorCallback(error_cb_);
}

void AudioRendererMixerInput::Stop()
{
    // Stop() may be called at any time, if Pause() hasn't been called we need to
    // remove our mixer input before shutdown.
    Pause();

    if (mixer_) {
        // TODO(dalecurtis): This is required so that |callback_| isn't called after
        // Stop() by an error event since it may outlive this ref-counted object. We
        // should instead have sane ownership semantics: http://crbug.com/151051
        mixer_->RemoveErrorCallback(error_cb_);
        mixer_pool_->ReturnMixer(mixer_);
        mixer_ = nullptr;
    }

    started_ = false;
}

void AudioRendererMixerInput::Play()
{
    if (playing_ || !mixer_)
        return;

    mixer_->AddMixerInput(params_, this);
    playing_ = true;
}

void AudioRendererMixerInput::Pause()
{
    if (!playing_ || !mixer_)
        return;

    mixer_->RemoveMixerInput(params_, this);
    playing_ = false;
}

bool AudioRendererMixerInput::SetVolume(double volume)
{
    base::AutoLock auto_lock(volume_lock_);
    volume_ = volume;
    return true;
}

OutputDeviceInfo AudioRendererMixerInput::GetOutputDeviceInfo()
{
    return mixer_
        ? mixer_->GetOutputDeviceInfo()
        : mixer_pool_->GetOutputDeviceInfo(owner_id_, 0 /* session_id */,
            device_id_, security_origin_);
}

bool AudioRendererMixerInput::CurrentThreadIsRenderingThread()
{
    return mixer_->CurrentThreadIsRenderingThread();
}

void AudioRendererMixerInput::SwitchOutputDevice(
    const std::string& device_id,
    const url::Origin& security_origin,
    const OutputDeviceStatusCB& callback)
{
    if (device_id == device_id_) {
        callback.Run(OUTPUT_DEVICE_STATUS_OK);
        return;
    }

    if (mixer_) {
        OutputDeviceStatus new_mixer_status = OUTPUT_DEVICE_STATUS_ERROR_INTERNAL;
        AudioRendererMixer* new_mixer = mixer_pool_->GetMixer(owner_id_, params_, latency_, device_id,
            security_origin, &new_mixer_status);
        if (new_mixer_status != OUTPUT_DEVICE_STATUS_OK) {
            callback.Run(new_mixer_status);
            return;
        }

        bool was_playing = playing_;
        Stop();
        device_id_ = device_id;
        security_origin_ = security_origin;
        mixer_ = new_mixer;
        mixer_->AddErrorCallback(error_cb_);
        started_ = true;

        if (was_playing)
            Play();

    } else {
        OutputDeviceStatus new_mixer_status = mixer_pool_
                                                  ->GetOutputDeviceInfo(owner_id_, 0 /* session_id */, device_id,
                                                      security_origin)
                                                  .device_status();
        if (new_mixer_status != OUTPUT_DEVICE_STATUS_OK) {
            callback.Run(new_mixer_status);
            return;
        }
        device_id_ = device_id;
        security_origin_ = security_origin;
    }

    callback.Run(OUTPUT_DEVICE_STATUS_OK);
}

double AudioRendererMixerInput::ProvideInput(AudioBus* audio_bus,
    uint32_t frames_delayed)
{
    TRACE_EVENT0("audio", "AudioRendererMixerInput::ProvideInput");
    const base::TimeDelta delay = AudioTimestampHelper::FramesToTime(frames_delayed, params_.sample_rate());

    int frames_filled = callback_->Render(delay, base::TimeTicks::Now(), 0, audio_bus);

    // AudioConverter expects unfilled frames to be zeroed.
    if (frames_filled < audio_bus->frames()) {
        audio_bus->ZeroFramesPartial(
            frames_filled, audio_bus->frames() - frames_filled);
    }

    // We're reading |volume_| from the audio device thread and must avoid racing
    // with the main/media thread calls to SetVolume(). See thread safety comment
    // in the header file.
    {
        base::AutoLock auto_lock(volume_lock_);
        return frames_filled > 0 ? volume_ : 0;
    }
}

void AudioRendererMixerInput::OnRenderError()
{
    callback_->OnRenderError();
}

} // namespace media
